package cn.virens.common.oauth.service;

import cn.virens.common.oauth.permission.VirensPermission;
import cn.virens.common.oauth.request.principal.DelegatePrincipal;
import cn.virens.common.oauth.request.principal.DelegatePrincipalResult;
import cn.virens.common.oauth.service.cache.AuthenticationCacheService;
import cn.virens.common.oauth.service.cookie.VirensCookieSerializer;
import cn.virens.common.oauth.service.resource.AuthenticationResourceService;
import cn.virens.common.oauth.subject.Subject;
import cn.virens.common.spring.bean.SpringBeanProxy;
import cn.virens.common.util.core.crypto.AesUtil;
import cn.virens.common.util.exception.APIException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.hutool.core.array.ArrayUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.session.web.http.CookieSerializer;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;

public class AuthenticationServiceImpl implements AuthenticationService, ApplicationContextAware, InitializingBean {
    private final VirensCookieSerializer serializer = new VirensCookieSerializer();

    private static final String CACHE_LOGIN = "auth-login";
    private static final String CACHE_PERM = "auth-perm";
    private static final String CACHE_ROLE = "auth-role";

    private AuthenticationResourceService mResourceService;
    private AuthenticationCacheService mCacheService;

    private Integer expire; // 缓存过期时间(天)
    private String aeskey; // 加密密钥

    public void setCookieHttponly(Boolean cookieHttponly) {
        this.serializer.setUseHttpOnlyCookie(cookieHttponly);
    }

    public void setCookieSameSite(String cookieName) {
        this.serializer.setSameSite(cookieName);
    }

    public void setCookieDomain(String cookieDomain) {
        this.serializer.setDomainName(cookieDomain);
    }

    public void setCookieSecure(boolean secure) {
        this.serializer.setUseSecureCookie(secure);
    }

    public void setCookiePath(String cookiePath) {
        this.serializer.setCookiePath(cookiePath);
    }

    public void setCookieName(String cookieName) {
        this.serializer.setCookieName(cookieName);
    }

    public void setExpire(Integer expire) {
        this.expire = expire;
    }

    public void setAeskey(String aeskey) {
        this.aeskey = aeskey;
    }

    @Override
    public Integer getExpire() {
        return expire;
    }

    @Override
    public String getAeskey() {
        return aeskey;
    }

    @Override
    public DelegatePrincipal refresh(DelegatePrincipal principal) throws APIException {
        mCacheService.put(CACHE_LOGIN, cacheKey(principal), principal);

        return principal;
    }

    @Override
    public Optional<DelegatePrincipal> authorization(HttpServletRequest request) throws APIException {
        return serializer.readCookieValue(request).map(str -> get(decrypt(str))); // 授权参数解密
    }

    @Override
    public boolean hasPermissions(Subject subject, String... permissions) throws APIException {
        for (String perm : cacheGet(CACHE_PERM, subject, mResourceService::getPermList)) {
            if (VirensPermission.resolve(perm).implies(permissions)) return true;
        }

        return false;
    }

    @Override
    public boolean hasRoles(Subject subject, String... roleIdentifiers) throws APIException {
        for (String role : cacheGet(CACHE_ROLE, subject, mResourceService::getRoleList)) {
            if (ArrayUtil.contains(roleIdentifiers, role)) return true;
        }

        return false;
    }

    @Override
    public <T extends DelegatePrincipalResult> T login(HttpServletRequest request, HttpServletResponse response, T result) throws APIException {
        CookieSerializer.CookieValue cookie = new CookieSerializer.CookieValue(request, response, encrypt(result.delegate()));

        // 填充信息过期时间-----------------------------------------------------------
        result.delegate().setRetryTime(LocalDateTime.now().plusDays(expire / 2));
        result.delegate().setExpireTime(LocalDateTime.now().plusDays(expire));
        result.delegate().setLoginTime(LocalDateTime.now());

        // 查询用户授权信息----------------------------------------------------------
        result.setRoleList(mResourceService.getRoleList(result.delegate()));
        result.setPermList(mResourceService.getPermList(result.delegate()));
        result.setLoginToken(serializer.getValue0(cookie));

        // 将cookie内容写入返回头中--------------------------------------------------
        this.serializer.writeCookieValue(cookie);
        this.serializer.writeHeadValue(cookie);

        // 保存登录令牌到缓存中-------------------------------------------------------
        return saveToCache(result);
    }

    @Override
    public <T extends DelegatePrincipal> T logout(HttpServletRequest request, HttpServletResponse response, T principal) throws APIException {
        if (principal != null && StrUtil.isNotEmpty(principal.getLoginAuth())) {
            mCacheService.del(CACHE_LOGIN, cacheKey(principal));
            mCacheService.del(CACHE_ROLE, principal.getUuid());
            mCacheService.del(CACHE_PERM, principal.getUuid());
        } else {
            return null;
        }

        return principal;
    }

    /**
     * 根据登录令牌获取对应的缓存KEY
     */
    protected String cacheKey(DelegatePrincipal principal) throws APIException {
        return principal.getUuid() + ":" + principal.getLoginAuth();
    }

    /**
     * 对文本内容进行解密
     */
    protected String encrypt(DelegatePrincipal principal) throws APIException {
        return AesUtil.encrypt(cacheKey(principal), aeskey);
    }

    /**
     * 对文本内容进行加密
     */
    protected String decrypt(String str) throws APIException {
        return AesUtil.decrypt(str, aeskey);
    }

    /**
     * 根据登录信息获取指定的缓存：如果不存在会要求创建
     */
    protected List<String> cacheGet(String name, Subject key, Function<DelegatePrincipal, List<String>> fun) {
        return mCacheService.get(name, key.getUuid(), () -> fun.apply(key.delegate()));
    }

    /**
     * 根据登录令牌创建登录缓存
     */
    protected <T extends DelegatePrincipalResult> T saveToCache(T result) throws APIException {
        mCacheService.put(CACHE_ROLE, result.delegate().getUuid(), result.getRoleList());
        mCacheService.put(CACHE_PERM, result.delegate().getUuid(), result.getPermList());
        mCacheService.put(CACHE_LOGIN, cacheKey(result.delegate()), result.delegate());

        return result;
    }

    /**
     * 创建对应的认证Cookie
     */
    protected CookieSerializer.CookieValue createCookie(HttpServletRequest request, HttpServletResponse response, DelegatePrincipal principal) throws APIException {
        return new CookieSerializer.CookieValue(request, response, encrypt(principal));
    }

    /**
     * 根据缓存获取登录令牌
     */
    protected DelegatePrincipal get(String cacheKey) throws APIException {
        return mCacheService.get(CACHE_LOGIN, cacheKey);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.mCacheService = SpringBeanProxy.proxy(AuthenticationCacheService.class, applicationContext);
        this.mResourceService = SpringBeanProxy.proxy(AuthenticationResourceService.class, applicationContext);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        this.serializer.setCookieMaxAge(3600 * 24 * 7);
        this.serializer.setUseBase64Encoding(false);
    }
}
